package com.example.filter;

import cn.hutool.jwt.JWT;
import com.alibaba.fastjson.JSON;
import com.example.common.Result;
import com.example.exception.TokenException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import javax.print.DocFlavor;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.function.Consumer;

import static com.example.constant.Constant.*;

/**
 * @author itheima
 */
// 1. 使用Order注解，设置执行顺序
@Order(-1)
@Component
@Slf4j
// 2. 要实现GlobalFilter声明全局过滤器
public class AuthorizeFilter implements GlobalFilter {
    @Resource
    private PassToken passToken;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        // MultiValueMap这个结构,是Map结构的变种,它允许存在相同名字的多个key
        MultiValueMap<String, String> params = exchange.getRequest().getHeaders();

        //2.判断哪些路径需要放行

        //2.0.1使用自定义集合方式  注:这是写死代码的方式
//        List<String> whiteList = new ArrayList<>();
//        whiteList.add("/users/login");

        //2.0.2使用配置导入方式  注:优化版
        List<String> whiteList = passToken.getWhite();

        //针对个别接口放行的功能
        // 2.1.获取当前请求的地址
        String url = exchange.getRequest().getPath().toString();
        // 2.2.判断这个地址是不是要放行
        if (whiteList.contains(url)) {
            log.info(url + "放行了");
            // 请求头放入放行标识
            storedHeader(exchange, TOKEN_USER_ID, GATEWAY_PASS);
            return chain.filter(exchange);
        }

        //2.3.需要校验token的路径
        // 获取头部的token
        // 为什么token是从这个参数获取的?
        // 因为这是规范
        String token = params.getFirst(REQUEST_HEADER);

        log.info("token为{}", token);
        //判断token是否为空
        if (!StringUtils.hasText(token)) {
            return unauthorized(exchange, "用户未登录");
        }

        //3.开始验证token
        // 密钥
        byte[] key = TOKEN_KEY.getBytes();
        // 默认验证HS265的算法
        boolean verify = JWT.of(token).setKey(key).verify();
        if (!verify) {
            log.error("token验证错误");
            return unauthorized(exchange, "授权信息有误");
        }

        JWT jwt = JWT.of(token);
        String userId = (String) jwt.getPayload(TOKEN_USER_ID);
        if (userId == null) {
            return unauthorized(exchange, "授权异常");
        }

        storedHeader(exchange, TOKEN_USER_ID, userId);

        // 放行
        log.info(url + "放行成功");
        return chain.filter(exchange);
    }

    /**
     * 更新和存放请求头
     */
    private void storedHeader(ServerWebExchange exchange, String header, String value) {
        //把解析到的用户信息,存放到请求头上
        // 因为响应的请求头是只读状态,不能在上面set
        // 1.重构一个响应对象,在重构的响应对象里面,设置请求头
        // 将userId添加到请求头上
        ServerHttpRequest requestNew = exchange.getRequest().mutate().headers(new Consumer<HttpHeaders>() {
            @Override
            public void accept(HttpHeaders httpHeaders) {
                httpHeaders.add(TOKEN_USER_ID, value);
            }
        }).build();
        // 2.刷新请求头
        exchange.mutate().request(requestNew).build();
    }

    private Mono<Void> unauthorized(ServerWebExchange exchange, String error) {
        //阻止(标准:给前端返回401状态码)
        error = StringUtils.hasText(error) ? error : "鉴权异常";
        exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
        Result data = Result.error(error);
        DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(JSON.toJSONString(data).getBytes(StandardCharsets.UTF_8));
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //指定编码，否则在浏览器中会中文乱码
        response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        return response.writeWith(Mono.just(buffer));
    }
}